在串接API時,遇到最大的坎就是Message內文加密了,
就讓我們來試看看囉~
項目 | 說明 |
---|---|
產出JSON訊息內文 | 即要送出的訊息內文(JSON) |
HashID | 由四組 Hash 值透過兩兩 XOR 位元運算再相加的32 位元字串。 |
IV值 | Nonce 值經過 SHA256 運算後取右邊 16 位元字串 。 |
有上一篇的經驗,IV計算就顯得非常親切,步驟如下:
sha256 = hashlib.sha256()
sha256.update(NonceValue.encode('utf-8'))
SHAValue = sha256.hexdigest().upper()
print(SHAValue)
IVValue=SHAValue[-16:]
print(IVValue)
結果
CB6FA68E42B655AB
重頭戲來囉,規格書顯示:
將訊息內文以 AES CBC 方式加密,加密後的 Byte 以十六進制2位數字串相加。
因為對於實在是沒有經驗,直接針對關鍵字搜尋,下面是我最後參考的一篇文章,
Python AES/CBC/PKCS5Padding加解密
不過雖然使用以上程式,可以正常的加解密,但出來的加密內容,與範例不一樣,因為不熟悉加解密的流程,看範例程式好像也沒有太大頭緒,大概是我PHP也不太會寫,只好於線上搜尋相關問題了,
最後最後~突發奇想,想說找看看線上加密的網頁,
參考如下
線上加解密網址
與網頁上的程式結果一致,但不是我要的答案,這也是友人問我的問題,無法跟範例一致。
結果我發現線上加解密網站,多了一些選項,Output Text Format的選項,多了HEX選項,如下圖,
絕大多數的線上範例,幾乎都是base64的格式,
登登,果然改用HEX之後柳暗花明,終於找到答案了!!!!
規格書上若能說出是用HEX轉碼就省很多麻煩了~~
首先先安裝以下套件
pip install pycryptodome
程式如下
from Crypto.Cipher import AES
class AESCrypt:
"""
AES/CBC/PKCS5Padding 加密
"""
def __init__(self, key,iv):
"""
使用金鑰,加密模式進行初始化
:param key:
"""
if len(key) != 16 and len(key) !=32:
raise RuntimeError('金鑰長度非16位 and 32 位!!!')
self.key = str.encode(key)
self.iv = str.encode(iv)
self.MODE = AES.MODE_CBC
self.block_size = 16
# 填充函數
# self.padding = lambda data: data + (self.block_size - len(data) % self.block_size) * chr(self.block_size - len(data) % self.block_size)
# 此處為一坑,需要現將data轉換為byte再來做填充,否則中文特殊字元等會報錯
self.padding = lambda data: data + (self.block_size - len(data.encode('utf-8')) % self.block_size) * chr(self.block_size - len(data.encode('utf-8')) % self.block_size)
# 截斷函數
self.unpadding = lambda data: data[:-ord(data[-1])]
def aes_encrypt(self, plaintext):
"""
加密
:param plaintext: 明文
:return:
"""
try:
# 填充16位
padding_text = self.padding(plaintext).encode("utf-8")
# 初始化加密器
cryptor = AES.new(self.key, self.MODE, self.iv)
# 進行AES加密
encrypt_aes = cryptor.encrypt(padding_text)
# 進行HEX轉碼
encrypt_text = encrypt_aes.hex()
# # 進行BASE64轉碼
# encrypt_text = (base64.b64encode(encrypt_aes)).decode()
return encrypt_text
except Exception as e:
logging.exception(e)
def aes_decrypt(self, ciphertext):
"""
解密
:param ciphertext: 密文
:return:
"""
try:
# 密文必須是16byte的整數倍
# if len(ciphertext) % 16 != 0:
# raise binascii.Error('密文錯誤!')
# print(ciphertext)
cryptor = AES.new(self.key, self.MODE, self.iv)
# 進行BASE64轉碼
# plain_decode = base64.b64decode(ciphertext)
# 進行HEX轉碼
plain_decode = bytes.fromhex(ciphertext)
# print(type(plain_decode)) #byte
# 進行ASE解密
decrypt_text = cryptor.decrypt(plain_decode)
# 截取
plain_text = self.unpadding(decrypt_text.decode("utf-8"))
return plain_text
except UnicodeDecodeError as e:
logging.error('解密失敗,請檢查金鑰是否正確!')
logging.exception(e)
except binascii.Error as e:
logging.exception(e)
except Exception as e:
logging.exception(e)
if __name__ == '__main__':
# 測試
send_message_ori = {
"ShopNo": "BA0026_001",
"OrderNo": "A201804270001",
"Amount": 50000,
"CurrencyID": "TWD",
"PayType": "A",
"ATMParam": {"ExpireDate": "20180502"},
"CardParam": {},
"PrdtName": "虛擬帳號訂單",
"ReturnURL": "http://10.11.22.113:8803/QPay.ApiClient/Store/Return",
"BackendURL": "http://10.11.22.113:8803/QPay.ApiClient/AutoPush/PushSuccess",
}
cryptor = AESCrypt(hashID,IVValue)
jsonText=json.dumps(send_message_ori, ensure_ascii=False).replace(' ', "")
aes_encrypt_str = cryptor.aes_encrypt(jsonText).upper()
print(f'加密結果為: {aes_encrypt_str}')
aes_decrypt_str = cryptor.aes_decrypt(aes_encrypt_str)
print(f'解密結果為: {aes_decrypt_str}')
結果如附
加密結果為: 2C236A4E91DB2F7670E79BBCE3A626EB728916919012681FF92BE0B4BBF57F5519AF1A469A1D8710B202CB2C2F3C12A770788D825AD0F0A22AED518545A0D244AD0F9C37C7C693EFFABE78B606BCDAED6284902F7F522BBA85D9BE7EFEF46C6793FB6A5D6624C2642A74EB312034BEA931EE3A5F3C660F3ABAA9032949AE86DEFEB452545807561D282C7B7C8E9102CED1404B8B542BC09CE12FA38F335BE7F027AE74BDDBADDB1790B172EFBF1FD25524E2BB64A626EA44643D4BD490E348E926BB7A48D5FA939EEC5BE681009E7AC7FED1C8475B715891321406960675B5A216032CF8657A3CB2B2D0C7FF85027D70E1F2B5DD414373912E97FA6FB85E9AB89B118BC545583CC9AC503F8BAD73C185CB97B28313618021F9217A30278043EF728BB5C49D231C4A22279864F68194254BC624789F36CCDEE75861CFC667CD8E9E89F1DB04ABA0D26FEF24BFE0470488
解密結果為: {"ShopNo":"BA0026_001","OrderNo":"A201804270001","Amount":50000,"CurrencyID":"TWD","PayType":"A","ATMParam":{"ExpireDate":"20180502"},"CardParam":{},"PrdtName":"虛擬帳號訂單","ReturnURL":"http://10.11.22.113:8803/QPay.ApiClient/Store/Return","BackendURL":"http://10.11.22.113:8803/QPay.ApiClient/AutoPush/PushSuccess"}
加解密的部分都搞定了,剩下呼叫永豐API的部分了